Skip to content

ci(slack): escape mrkdwn + fetch timeout (sync notifier with OGAD)#509

Merged
alichherawalla merged 1 commit into
mainfrom
fix/slack-notes-escape-timeout
Jul 9, 2026
Merged

ci(slack): escape mrkdwn + fetch timeout (sync notifier with OGAD)#509
alichherawalla merged 1 commit into
mainfrom
fix/slack-notes-escape-timeout

Conversation

@alichherawalla

@alichherawalla alichherawalla commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Syncs scripts/notify-slack-release.mjs with the OGAD copy: escape & < > in the note body (raw commit subjects can misrender / be read as a Slack link) + AbortSignal.timeout(10000) so a hung Slack endpoint can't stall the release job. Fail-soft unchanged. Keeps the two repos' notifiers identical.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved release announcement formatting so release notes and product names display safely in Slack.
    • Added a timeout to release announcement requests to reduce the chance of stalled notifications.
    • When no release notes are available, messages now show a clear fallback instead of sending empty content.

Keeps the shared notifier identical across repos: escape & < > in the note
body, AbortSignal.timeout(10000) on the POST. Matches off-grid-ai-desktop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@alichherawalla alichherawalla merged commit d4b2525 into main Jul 9, 2026
1 check was pending
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: aab5766b-03b1-4bbe-a6d3-1be7c652bfdc

📥 Commits

Reviewing files that changed from the base of the PR and between 8322e22 and 2423479.

📒 Files selected for processing (1)
  • scripts/notify-slack-release.mjs

📝 Walkthrough

Walkthrough

Updates scripts/notify-slack-release.mjs to escape release notes and product text for Slack mrkdwn safety via a new esc() helper, adjusts message body fallback logic, and adds a 10-second AbortSignal timeout to Slack webhook and postMessage fetch calls.

Changes

Slack release script safety fixes

Layer / File(s) Summary
Mrkdwn escaping and body fallback
scripts/notify-slack-release.mjs
Adds an esc() helper to escape notes and product before embedding in Slack mrkdwn text, and updates the message body to use escaped notes or fall back to a fixed "No release notes generated…" string.
Fetch request timeouts
scripts/notify-slack-release.mjs
Adds signal: AbortSignal.timeout(10000) to both the incoming webhook fetch and the chat.postMessage fetch requests.

Estimated code review effort: 1 (Trivial) | ~5 minutes

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/slack-notes-escape-timeout

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

scripts/notify-slack-release.mjs

Parsing error: Unexpected token {


Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

CI: Escape Slack mrkdwn and add 10s timeout to release notifier

🐞 Bug fix ✨ Enhancement 🕐 10-20 Minutes

Grey Divider

AI Description

• Escape reserved mrkdwn characters in Slack release note text to prevent misrendered subjects.
• Add a 10s fetch abort signal so Slack outages can’t stall the release job.
• Keep notifier behavior and fail-soft semantics aligned with the OGAD script.
Diagram

graph TD
  GA["Release workflow"] --> Script["notify-slack-release.mjs"]
  Script --> Webhook{{"Slack Webhook"}}
  Script --> API{{"Slack Web API"}}
  subgraph Legend
    direction LR
    _job["CI job"] ~~~ _file["Script file"] ~~~ _ext{{"External service"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Centralize notifier as a shared GitHub Action/package
  • ➕ Eliminates cross-repo drift and manual syncing
  • ➕ Enables consistent versioning, testing, and rollout
  • ➖ Introduces publishing/version-management overhead
  • ➖ May be harder to hotfix quickly during a release incident
2. Use a Slack formatting/escaping utility or SDK
  • ➕ Reduces risk of missing additional escaping/formatting edge cases
  • ➕ Potentially clearer intent than custom escaping
  • ➖ Adds dependency surface area for a tiny script
  • ➖ Still requires care to avoid escaping intentional links

Recommendation: The PR’s approach is the best fit for the stated goal (keep parity with OGAD while fixing two concrete reliability/rendering issues) with minimal scope and no new dependencies. Longer-term, consider centralizing the notifier to prevent future divergence across repos.

Files changed (1) +9 / -2

Bug fix (1) +9 / -2
notify-slack-release.mjsEscape mrkdwn note text and abort Slack POSTs after 10s +9/-2

Escape mrkdwn note text and abort Slack POSTs after 10s

• Adds a small mrkdwn escape helper for '& < >' and applies it to the product name and generated note body while leaving the explicit '<url|label>' link intact. Adds 'AbortSignal.timeout(10000)' to both Slack POST paths (webhook and chat.postMessage) to prevent hangs, preserving existing fail-soft behavior.

scripts/notify-slack-release.mjs

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (1) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 36 rules

Grey Divider


Remediation recommended

1. Slack notifier missing regression test 📘 Rule violation ▣ Testability
Description
The PR changes Slack message escaping and adds fetch timeouts, which alters release-notification
behavior but does not include an automated regression test to pin the new behavior. This risks
silent regressions in the release workflow and violates the requirement to add/update tests for
behavior changes.
Code

scripts/notify-slack-release.mjs[R38-54]

+// Slack mrkdwn reserves & < > — a raw commit subject containing them (release notes are raw
+// commit subjects) would misrender or be read as a <link|mention>. Escape the note body only;
+// NOT the intentional <url|label> link line below.
+const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+
let notes = '';
try { notes = readFileSync(notesFile, 'utf8').trim(); } catch { /* notes optional */ }
// Slack section text caps at 3000 chars; keep well under and never dump a wall.
if (notes.length > 2600) { notes = `${notes.slice(0, 2600)}\n…`; }

const tag = label ? `  \`${label}\`` : '';
-const header = `:package:  *${product}*  \`${version || 'release'}\`${tag}`;
+const header = `:package:  *${esc(product)}*  \`${version || 'release'}\`${tag}`;
const linkLine = releaseUrl ? `<${releaseUrl}|Download / release page>` : '';
-const body = notes || '_No release notes generated for this build._';
+const body = notes ? esc(notes) : '_No release notes generated for this build._';

const blocks = [
  { type: 'section', text: { type: 'mrkdwn', text: header } },
Relevance

⭐⭐ Medium

Test-add requests often rejected (Codecov in #233/#357), but focused regression tests accepted
(#459).

PR-#233
PR-#357
PR-#459

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1574110 requires a regression test for each bug fix or behavior change. The diff
adds escaping via esc(...) and adds AbortSignal.timeout(10000) to Slack POST requests, changing
message formatting and network behavior without a corresponding test change in this PR.

Rule 1574110: Require regression test for each bug fix or behavior change
scripts/notify-slack-release.mjs[38-78]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`scripts/notify-slack-release.mjs` behavior changed (mrkdwn escaping + request timeout), but there is no regression test added/updated in this PR to ensure the new behavior stays correct.

## Issue Context
This script runs in release workflows and is part of the release job’s observable behavior (Slack message formatting and network-call timeout). A regression test should verify escaping of `&`, `<`, `>` in the note/body/header and ensure fetch calls include a timeout signal.

## Fix Focus Areas
- scripts/notify-slack-release.mjs[38-78]
- __tests__/unit/scripts/notify-slack-release.test.ts[1-200]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Escape breaks length cap 🐞 Bug ≡ Correctness
Description
In notify-slack-release.mjs, release notes are truncated to 2600 characters before being escaped,
but esc() can expand the text (e.g., '&' → '&amp;') and push the final mrkdwn section over the
3000-character Slack limit. This can cause Slack to reject the block/payload and drop the release
announcement despite the pre-escape truncation.
Code

scripts/notify-slack-release.mjs[R41-51]

+const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+
let notes = '';
try { notes = readFileSync(notesFile, 'utf8').trim(); } catch { /* notes optional */ }
// Slack section text caps at 3000 chars; keep well under and never dump a wall.
if (notes.length > 2600) { notes = `${notes.slice(0, 2600)}\n…`; }

const tag = label ? `  \`${label}\`` : '';
-const header = `:package:  *${product}*  \`${version || 'release'}\`${tag}`;
+const header = `:package:  *${esc(product)}*  \`${version || 'release'}\`${tag}`;
const linkLine = releaseUrl ? `<${releaseUrl}|Download / release page>` : '';
-const body = notes || '_No release notes generated for this build._';
+const body = notes ? esc(notes) : '_No release notes generated for this build._';
Relevance

⭐⭐ Medium

No historical evidence on Slack length cap vs escaping expansion in
scripts/notify-slack-release.mjs.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The code comments state Slack section text caps at 3000 chars and truncates notes to 2600 chars,
but the escaping that can increase length is applied afterward when building body from
esc(notes).

scripts/notify-slack-release.mjs[38-56]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The script truncates `notes` to 2600 chars before escaping `&`, `<`, `>` via `esc()`. Because escaping expands characters into multi-byte entities, the final `body` text can exceed Slack’s 3000-char limit for section text, causing the announcement request to fail.

### Issue Context
This script is explicitly trying to stay under Slack’s 3000-character cap, but the cap is currently enforced on the *unescaped* text rather than the final rendered/posted mrkdwn string.

### Fix Focus Areas
- scripts/notify-slack-release.mjs[41-51]

### Recommended fix
Implement an `escapeAndTruncateMrkdwn(text, maxLen)` helper that:
1) Escapes `&`, `<`, `>` while building the output.
2) Stops when the *escaped output* reaches `maxLen` (e.g. 2600).
3) Appends `\n…` when truncation occurs.

This avoids post-escape length expansion and avoids slicing in the middle of an escape sequence (e.g., cutting `&amp;` into `&am`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

3. Escape comment mismatch 🐞 Bug ⚙ Maintainability
Description
The new comment says only the note body is escaped, but esc() is also applied to the header product
string. This mismatch is misleading for future maintainers reading the comment to understand what is
(and isn’t) escaped.
Code

scripts/notify-slack-release.mjs[R38-49]

+// Slack mrkdwn reserves & < > — a raw commit subject containing them (release notes are raw
+// commit subjects) would misrender or be read as a <link|mention>. Escape the note body only;
+// NOT the intentional <url|label> link line below.
+const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+
let notes = '';
try { notes = readFileSync(notesFile, 'utf8').trim(); } catch { /* notes optional */ }
// Slack section text caps at 3000 chars; keep well under and never dump a wall.
if (notes.length > 2600) { notes = `${notes.slice(0, 2600)}\n…`; }

const tag = label ? `  \`${label}\`` : '';
-const header = `:package:  *${product}*  \`${version || 'release'}\`${tag}`;
+const header = `:package:  *${esc(product)}*  \`${version || 'release'}\`${tag}`;
Relevance

⭐⭐⭐ High

Team often accepts fixing misleading comments/docs (e.g., #178, #179, #181).

PR-#178
PR-#179
PR-#181

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The comment claims only the note body is escaped, but the header is constructed using
*${esc(product)}* which applies escaping to the product string too.

scripts/notify-slack-release.mjs[38-51]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
A comment claims the script escapes "the note body only", but the code also escapes `product` in the header. This is a documentation/maintainability issue that can confuse future edits.

### Issue Context
Escaping is correctly *not* applied to the explicit Slack link markup line (`<url|label>`). The comment should reflect that the non-link text portions (header/body) may be escaped.

### Fix Focus Areas
- scripts/notify-slack-release.mjs[38-49]

### Recommended fix
Update the comment to reflect the actual intent, e.g.:
- “Escape Slack-reserved `& < >` in user-controlled text (header + notes). Do not escape the explicit `<url|label>` link markup line below.”

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +38 to 54
// Slack mrkdwn reserves & < > — a raw commit subject containing them (release notes are raw
// commit subjects) would misrender or be read as a <link|mention>. Escape the note body only;
// NOT the intentional <url|label> link line below.
const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');

let notes = '';
try { notes = readFileSync(notesFile, 'utf8').trim(); } catch { /* notes optional */ }
// Slack section text caps at 3000 chars; keep well under and never dump a wall.
if (notes.length > 2600) { notes = `${notes.slice(0, 2600)}\n…`; }

const tag = label ? ` \`${label}\`` : '';
const header = `:package: *${product}* \`${version || 'release'}\`${tag}`;
const header = `:package: *${esc(product)}* \`${version || 'release'}\`${tag}`;
const linkLine = releaseUrl ? `<${releaseUrl}|Download / release page>` : '';
const body = notes || '_No release notes generated for this build._';
const body = notes ? esc(notes) : '_No release notes generated for this build._';

const blocks = [
{ type: 'section', text: { type: 'mrkdwn', text: header } },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Slack notifier missing regression test 📘 Rule violation ▣ Testability

The PR changes Slack message escaping and adds fetch timeouts, which alters release-notification
behavior but does not include an automated regression test to pin the new behavior. This risks
silent regressions in the release workflow and violates the requirement to add/update tests for
behavior changes.
Agent Prompt
## Issue description
`scripts/notify-slack-release.mjs` behavior changed (mrkdwn escaping + request timeout), but there is no regression test added/updated in this PR to ensure the new behavior stays correct.

## Issue Context
This script runs in release workflows and is part of the release job’s observable behavior (Slack message formatting and network-call timeout). A regression test should verify escaping of `&`, `<`, `>` in the note/body/header and ensure fetch calls include a timeout signal.

## Fix Focus Areas
- scripts/notify-slack-release.mjs[38-78]
- __tests__/unit/scripts/notify-slack-release.test.ts[1-200]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +41 to +51
const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');

let notes = '';
try { notes = readFileSync(notesFile, 'utf8').trim(); } catch { /* notes optional */ }
// Slack section text caps at 3000 chars; keep well under and never dump a wall.
if (notes.length > 2600) { notes = `${notes.slice(0, 2600)}\n…`; }

const tag = label ? ` \`${label}\`` : '';
const header = `:package: *${product}* \`${version || 'release'}\`${tag}`;
const header = `:package: *${esc(product)}* \`${version || 'release'}\`${tag}`;
const linkLine = releaseUrl ? `<${releaseUrl}|Download / release page>` : '';
const body = notes || '_No release notes generated for this build._';
const body = notes ? esc(notes) : '_No release notes generated for this build._';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Escape breaks length cap 🐞 Bug ≡ Correctness

In notify-slack-release.mjs, release notes are truncated to 2600 characters before being escaped,
but esc() can expand the text (e.g., '&' → '&amp;') and push the final mrkdwn section over the
3000-character Slack limit. This can cause Slack to reject the block/payload and drop the release
announcement despite the pre-escape truncation.
Agent Prompt
### Issue description
The script truncates `notes` to 2600 chars before escaping `&`, `<`, `>` via `esc()`. Because escaping expands characters into multi-byte entities, the final `body` text can exceed Slack’s 3000-char limit for section text, causing the announcement request to fail.

### Issue Context
This script is explicitly trying to stay under Slack’s 3000-character cap, but the cap is currently enforced on the *unescaped* text rather than the final rendered/posted mrkdwn string.

### Fix Focus Areas
- scripts/notify-slack-release.mjs[41-51]

### Recommended fix
Implement an `escapeAndTruncateMrkdwn(text, maxLen)` helper that:
1) Escapes `&`, `<`, `>` while building the output.
2) Stops when the *escaped output* reaches `maxLen` (e.g. 2600).
3) Appends `\n…` when truncation occurs.

This avoids post-escape length expansion and avoids slicing in the middle of an escape sequence (e.g., cutting `&amp;` into `&am`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +38 to +49
// Slack mrkdwn reserves & < > — a raw commit subject containing them (release notes are raw
// commit subjects) would misrender or be read as a <link|mention>. Escape the note body only;
// NOT the intentional <url|label> link line below.
const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');

let notes = '';
try { notes = readFileSync(notesFile, 'utf8').trim(); } catch { /* notes optional */ }
// Slack section text caps at 3000 chars; keep well under and never dump a wall.
if (notes.length > 2600) { notes = `${notes.slice(0, 2600)}\n…`; }

const tag = label ? ` \`${label}\`` : '';
const header = `:package: *${product}* \`${version || 'release'}\`${tag}`;
const header = `:package: *${esc(product)}* \`${version || 'release'}\`${tag}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational

3. Escape comment mismatch 🐞 Bug ⚙ Maintainability

The new comment says only the note body is escaped, but esc() is also applied to the header product
string. This mismatch is misleading for future maintainers reading the comment to understand what is
(and isn’t) escaped.
Agent Prompt
### Issue description
A comment claims the script escapes "the note body only", but the code also escapes `product` in the header. This is a documentation/maintainability issue that can confuse future edits.

### Issue Context
Escaping is correctly *not* applied to the explicit Slack link markup line (`<url|label>`). The comment should reflect that the non-link text portions (header/body) may be escaped.

### Fix Focus Areas
- scripts/notify-slack-release.mjs[38-49]

### Recommended fix
Update the comment to reflect the actual intent, e.g.:
- “Escape Slack-reserved `& < >` in user-controlled text (header + notes). Do not escape the explicit `<url|label>` link markup line below.”

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant